#include "User.h"

void User::init(int argc, char* argv[]) {
    logfile.open("log.txt", ios_base::app | ios_base::out);

    logfile << "----------------------------------------------------------------\n";

    time_t rawtime = time(0);
    srand(time(0));
    logfile << "cs294-13 proj4 started " << asctime(localtime(&rawtime));

    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(640,480);
	glutCreateWindow("cs294-13 proj4");

    cloth = 0;

    timestep = 0.01;
    maxSpeed = 1;

    //config file
    char* configFilename = "config.txt";
    if (argc >= 2) configFilename = argv[1];
    useConfigFile(configFilename);

    eyePhi = 0;
    eyeTheta = 1;
    eyeDist = 10;
    
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glShadeModel(GL_SMOOTH);
    GLfloat ambientLight[] = { 0.125f, 0.125f, 0.125f, 1.0f };
    GLfloat diffuseLight[] = { 0.875f, 0.875f, 0.875f, 1.0f };
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

    GLfloat mcolor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mcolor);

    polygonMode = GL_FILL;
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    active = false;

    totalIterations = 0;
}

//macro for readability
#define PROCESS_OP(op_name) \
    if (!strcmp(op, #op_name)) { \
        op_ ## op_name ## (configfile); \
        continue; \
    }

void User::useConfigFile(const char *filename) {
    ifstream configfile(filename);
    char op[256];
    
    while (true) {
        configfile >> op;
        if (!configfile.good()) break;

        PROCESS_OP(cloth);
        PROCESS_OP(timestep);
        PROCESS_OP(maxSpeed);
        PROCESS_OP(k);
        PROCESS_OP(g);
        PROCESS_OP(stiffnessDamping);
        PROCESS_OP(massDamping);
        
        logfile << "Unrecognized op '" << op << "'\n";
    }

    configfile.close();

    if (!cloth) {
        logfile << "No cloth parameters given.\n";
        exit(0);
    }
}

void User::op_cloth(ifstream& configfile) {
    int numX, numY;
    double interval;
    configfile >> numX >> numY >> interval;

    Vector3d base = Vector3d(-0.5 * interval * numX, 0.5 * interval * numY, 0);

    cloth = new HangingCloth(numX, numY, base, Vector3d(interval, 0, 0), Vector3d(0, 0, interval), ElasticMaterial());

    logfile << "Cloth: " << numX << "x" << numY << " with spacing " << interval << ".\n";
}

void User::op_timestep(ifstream& configfile) {
    double value;
    configfile >> value;

    timestep = value;

    logfile << "Timestep set to " << timestep << ".\n";
}

void User::op_maxSpeed(ifstream& configfile) {
    double value;
    configfile >> value;

    maxSpeed = value;

    logfile << "Maximum speed set to " << maxSpeed << ".\n";
}

void User::op_k(ifstream& configfile) {
    double kx, ky;
    configfile >> kx, ky;
    cloth->material.kx = kx;
    cloth->material.ky = ky;

    logfile << "Spring constant set to " << kx << ", " << ky << ".\n";
}
void User::op_g(ifstream& configfile) {
    Vector3d value;
    configfile >> value;
    cloth->material.g = value;

    logfile << "Gravity set to " << value << ".\n";
}

void User::op_stiffnessDamping(ifstream& configfile) {
    double value;
    configfile >> value;
    cloth->material.stiffnessDamping = value;

    logfile << "Stiffness damping set to " << value << ".\n";
}

void User::op_massDamping(ifstream& configfile) {
    double value;
    configfile >> value;
    cloth->material.massDamping = value;

    logfile << "Mass damping set to " << value << ".\n";
}

void User::setPerspective() const {
    // Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	// Set the viewport to be the entire window
    glViewport(0, 0, w, h);
    gluPerspective(45, float(w) / float(h), 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    double eyex = sin(eyePhi) * sin(eyeTheta) * eyeDist;
    double eyey = cos(eyeTheta) * eyeDist;
    double eyez = cos(eyePhi) * sin(eyeTheta) * eyeDist;
	gluLookAt(eyex, eyey, eyez, 
		      0.0, 0.0, 0.0,
			  0.0, 1.0, 0.0);
}

void User::render() {
    clock_t elapsedClock = clock() - lastFrame;
    double dt = double(elapsedClock) / CLOCKS_PER_SEC;
    lastFrame = clock();

    if (dt > 1) dt = 1;

    if (mouseButton == GLUT_LEFT_BUTTON) {
        eyePhi -= diffMouseX * 0.01;
        eyeTheta -= diffMouseY * 0.01;
        if (eyeTheta < 0.01) eyeTheta = 0.01;
        else if (eyeTheta > pi - 0.01) eyeTheta = pi - 0.01;
    } else if (mouseButton == GLUT_RIGHT_BUTTON) {
        Vector3d diff = Vector3d(diffMouseX * 0.01, 0, diffMouseY * 0.01);
        cloth->move(diff);
    } else if (mouseButton == GLUT_MIDDLE_BUTTON) {
        double angleLeft = diffMouseX * 0.002;
        double angleUp = -diffMouseY * 0.002;
        cloth->rotate(angleLeft, angleUp);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    setPerspective();

    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);

    clock_t simStart = clock();

    if (active) {
        double elapsedSimTime;
        int iterations = 0;
        do {
            cloth->velocityVerletUpdate(timestep);
            iterations++;
            totalIterations++;
            elapsedSimTime = double(clock() - simStart) / CLOCKS_PER_SEC;
        } while (elapsedSimTime < dt && iterations < maxSpeed * dt / timestep);

        speed = iterations * timestep / dt;
    }

    cloth->render();

    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);

    glBegin(GL_LINES);
        glColor3f(1.0, 0.0, 0.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(1.0, 0.0, 0.0);

        glColor3f(0.0, 1.0, 0.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(0.0, 1.0, 0.0);

        glColor3f(0.0, 0.0, 1.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(0.0, 0.0, 1.0);
    glEnd();

    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);

	glutSwapBuffers();
}

void User::reshape(int w, int h) {
    // Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	if (h == 0) h = 1;

    this->h = h;
    this->w = w;
}

void User::keyboard(unsigned char key, int x, int y) {
    switch (key) {
        case 'x':
            logfile << "Total simulation steps: " << totalIterations << "\n";
            logfile << "Exiting.\n";
            exit(0);
            break;
        case 'w':
            eyeDist -= 0.25;
            break;
        case 'p':
            switch (polygonMode) {
                case GL_FILL:
                    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                    polygonMode = GL_LINE;
                    break;
                case GL_LINE:
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                    polygonMode = GL_FILL;
                    break;
            }
            break;
        case 's':
            eyeDist += 0.25;
            break;
        case ' ':
            active = !active;
            break;
        case 'i':
            printf("Speed: %.3f\n", speed);
            break;
    }
}

void User::mouseState(int button, int state, int x, int y) {
    if (state == GLUT_DOWN) {
        mouseButton = button;
        lastMouseX = x;
        lastMouseY = y;
    } else if (state == GLUT_UP) {
        mouseButton = -1;
    }
}

void User::mouseMotion(int x, int y) {
    if (mouseButton >= 0) {
        diffMouseX = x - lastMouseX;
        diffMouseY = y - lastMouseY;
    }
    lastMouseX = x;
    lastMouseY = y;
}
